﻿using DotNetty.Buffers;
using DotNetty.Codecs.Http;
using DotNetty.Codecs.Http.WebSockets;
using DotNetty.Transport.Channels;
using System.Text;

namespace WebSocketChatServer
{
    /// <summary>
    /// 在Netty中,SimpleChannelInboundHandler是一个抽象类,用于处理入站消息(Inbound Messages).
    /// 它是ChannelInboundHandlerAdapter的子类,为简化消息处理提供了方便的实现.
    /// SimpleChannelInboundHandler的主要作用是处理接收到的消息,并提供一种方便的方式来释放资源。
    /// 它使用了泛型,可以指定处理的消息类型,并自动进行类型转换.当接收到符合指定消息类型的消息时,
    /// SimpleChannelInboundHandler会自动调用channelRead0()方法来处理消息。
    /// </summary>
    public class WebSocketServerHandler : SimpleChannelInboundHandler<Object> // 这里泛型使用的是Object,表示它将处理接收到的Object类型的消息
    {
        // Channel就是服务端和客户端的建立的一个收发消息的通道(socket每有一个连接客户端就会创建一个channnel)
        #region 每一个客户端连接会开辟一个Channel,那么该Channel的处理程序就会new WebSocketServerHandler()对象绑定到这个Channel上,所以该实例的成员变量都和该Channel对应
        /// <summary>
        /// 客户端和服务器端交互会话的标志
        /// </summary>
        private string sessionid;
        /// <summary>
        /// 通道处理的上下文
        /// </summary>
        private IChannelHandlerContext ctx;
        /// <summary>
        /// websocket握手
        /// </summary>
        private WebSocketServerHandshaker handshaker;

        // 获取每个客户端的每次请求是第几次
        private int count;
        #endregion


        /// <summary>
        /// 当有新消息到达时,channelRead0()方法将被调用,可以在该方法中编写消息处理的逻辑
        /// </summary>
        /// <param name="ctx"></param>
        /// <param name="msg"></param>
        protected override void ChannelRead0(IChannelHandlerContext ctx, object msg)
        {
            count++;
            Console.WriteLine(ctx.Channel.RemoteAddress + "," + count); // 测试后发现每个客户端都有独立的count,因为该类的一个对象对应一个和客户端对应的Channel 
            // 如果是http请求
            if (msg is IFullHttpRequest request)
                handleHttpRequest(ctx, request);
            // 如果是websocket帧
            else if (msg is WebSocketFrame)
                handleWebSocketFrame(ctx, msg as WebSocketFrame);
        }

        /// <summary>
        /// 我们用的是websocket,为什么要用http来处理呢?
        /// 因为客户端首先要连接到服务器端,首先要发一个GET方法的HTTP请求过来
        /// </summary>
        /// <param name="ctx"></param>
        /// <param name="request"></param>
        private void handleHttpRequest(IChannelHandlerContext ctx, IFullHttpRequest request)
        {
            // 取出请求头Upgrade的值,赋值给变量upgrade
            // 客户端采用websocket,http请求头里会带:Upgrade: websocket
            request.Headers.TryGetAsString(new DotNetty.Common.Utilities.AsciiString("Upgrade"), out string upgrade);
            // 如果http请求不成功或者Upgrade的值不是websocket
            if (!request.Result.IsSuccess || (!"websocket".Equals(upgrade, StringComparison.OrdinalIgnoreCase)))
            {
                sendHttpResponse(ctx, request, new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.BadRequest));
                return;
            }
            // websocket握手的工厂类:WebSocketServerHandshakerFactory
            WebSocketServerHandshakerFactory factory = new WebSocketServerHandshakerFactory("ws://localhost/", null, false);
            // 每次连接都是一个新的握手
            handshaker = factory.NewHandshaker(request);
            // 判断握手是否成功
            if (handshaker == null)
            {
                // 响应告诉客户端它不支持该websocket版本
                WebSocketServerHandshakerFactory.SendUnsupportedVersionResponse(ctx.Channel);
            }
            else
            {
                // 如果握手成功
                handshaker.HandshakeAsync(ctx.Channel, request);
                this.ctx = ctx;

            }
        }

        private async void sendHttpResponse(IChannelHandlerContext ctx, IFullHttpRequest request, DefaultFullHttpResponse defaultFullHttpResponse)
        {
            // 响应码不为200,设置响应失败的信息
            if (defaultFullHttpResponse.Status.Code != 200)
            {
                IByteBuffer buf = Unpooled.CopiedBuffer(defaultFullHttpResponse.Status.ToString(), Encoding.UTF8);
                defaultFullHttpResponse.Content.WriteBytes(buf);
                // 释放buf
                buf.Release();
                // 设置响应头信息
                defaultFullHttpResponse.Headers.Set(new DotNetty.Common.Utilities.AsciiString("Content-Length"), defaultFullHttpResponse.Content.ReadableBytes);
            }

            // 将响应消息返回给浏览器
            await ctx.Channel.WriteAndFlushAsync(defaultFullHttpResponse);
        }

        private void handleWebSocketFrame(IChannelHandlerContext ctx, WebSocketFrame? frame)
        {
            // 如果是关闭websocket的消息
            if (frame is CloseWebSocketFrame)
            {
                handshaker.CloseAsync(ctx.Channel, frame as CloseWebSocketFrame);
                return;
            }

            // 如果是Ping的消息
            if (frame is PingWebSocketFrame)
            {
                // 响应Pong
                ctx.Channel.WriteAndFlushAsync(new PongWebSocketFrame(frame.Content));
                return;
            }

            // 如果不是文本消息
            if (!(frame is TextWebSocketFrame))
            {
                Console.WriteLine("当前只支持文本消息,不支持二进制消息");
                throw new Exception("当前只支持文本消息,不支持二进制消息");
            }

            try
            {
                // 判断消息类型
                RequestMsg requestMsg = RequestMsg.createInstance(((TextWebSocketFrame)frame).Text());
                ResponseMsg responseMsg = new ResponseMsg();
                // 设置响应消息类型为请求消息类型
                responseMsg.ServiceId = requestMsg.ServiceId;

                string requestId = requestMsg.RequestId;
                // 这里获取的sessionId,也是这个Channel唯一的。因为该类的一个对象对应一个和客户端对应的Channel 
                this.sessionid = requestId;

                //requestId不能为空
                if (string.IsNullOrEmpty(requestId))
                {
                    responseMsg.IsSuccess = false;
                    responseMsg.Message = "requestId不能为空";
                    sendWebSocket(responseMsg.ToString());
                    return;
                }


                // 上线
                if (Code.ONLINE == requestMsg.ServiceId)
                {
                    //Name不能为空
                    if (string.IsNullOrEmpty(requestMsg.Name))
                    {
                        responseMsg.IsSuccess = false;
                        responseMsg.Message = "Name不能为空";
                        sendWebSocket(responseMsg.ToString());
                        return;
                    }

                    // 如果客户端已经在在线列表中
                    if (BanabaService.banabaDic.ContainsKey(requestId))
                    {
                        responseMsg.IsSuccess = false;
                        responseMsg.Message = "你已经注册了,不能重复注册";
                        sendWebSocket(responseMsg.ToString());
                        return;
                    }

                    // 注册到服务器端
                    if (!BanabaService.regist(requestId, new BanabaService(ctx, requestMsg.Name)))
                    {
                        responseMsg.IsSuccess = false;
                        responseMsg.Message = "注册失败";
                        sendWebSocket(responseMsg.ToString());
                        return;
                    }

                    responseMsg.IsSuccess = true;
                    responseMsg.Message = "注册成功";
                    responseMsg.Name = requestMsg.Name;
                    responseMsg.RequestId = requestId;
                    responseMsg.HadOnline.Add(requestId, requestMsg.Name);
                    sendWebSocket(responseMsg.ToString());

                    // 通知其他已经上线的客户端,requestId这一个客户端已经上线
                    foreach (var item in BanabaService.banabaDic)
                    {
                        // 如果不是自己,则发送
                        if (!item.Key.Equals(requestId))
                        {
                            RequestMsg msg = new RequestMsg();
                            msg.ServiceId = Code.ONLINE;
                            msg.RequestId = requestId;
                            msg.Name = requestMsg.Name;
                            try
                            {
                                item.Value.send(msg);
                            }
                            catch (Exception ex)
                            {
                                Console.WriteLine("发送消息给客户端异常:" + ex.Message);
                                throw;
                            }
                        }
                    }
                }
                else if (Code.SEND_MESSAGE == requestMsg.ServiceId) // 发送数据
                {
                    //Name不能为空
                    if (string.IsNullOrEmpty(requestMsg.Name))
                    {
                        responseMsg.IsSuccess = false;
                        responseMsg.Message = "Name不能为空";
                        sendWebSocket(responseMsg.ToString());
                        return;
                    }

                    // 判断发送的消息是否为空
                    if (string.IsNullOrEmpty(requestMsg.Message))
                    {
                        responseMsg.IsSuccess = false;
                        responseMsg.Message = "message不能为空";
                        sendWebSocket(responseMsg.ToString());
                        return;
                    }

                    responseMsg.IsSuccess = true;
                    responseMsg.Message = "发送消息成功";
                    sendWebSocket(responseMsg.ToString());

                    // 向所有在线客户端发送消息
                    foreach (var item in BanabaService.banabaDic)
                    {
                        RequestMsg msg = new RequestMsg();
                        msg.ServiceId = Code.RECEIVE_MESSAGE;
                        msg.Name = requestMsg.Name;
                        msg.RequestId = requestId;
                        msg.Message = requestMsg.Message;
                        try
                        {
                            item.Value.send(msg);
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine("回调发送消息给客户端异常:" + ex.Message);
                        }
                    }
                }
                else if (Code.OUTLINE == requestMsg.ServiceId) // 下线
                {
                    BanabaService.logout(requestId);
                    responseMsg.IsSuccess = true;
                    responseMsg.Message = "下线成功";
                    sendWebSocket(responseMsg.ToString());
                    BanabaService.notifyOutline(requestId);
                }
                else
                {
                    responseMsg.IsSuccess = false;
                    responseMsg.Message = "未知请求";
                    sendWebSocket(responseMsg.ToString());
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message + "\r\n" + ex.StackTrace);
            }
        }

        private void sendWebSocket(string v)
        {
            // this.ctx.Removed:通道上下文是否已被移除,被移除为true
            if (this.handshaker == null || this.ctx == null || this.ctx.Removed)
            {
                Console.WriteLine("尚未握手成功,无法向客户端发送WebSocket消息");
                throw new Exception("尚未握手成功,无法向客户端发送WebSocket消息");
            }
            this.ctx.Channel.WriteAsync(new TextWebSocketFrame(v));
            this.ctx.Flush();
        }

        public override void ChannelReadComplete(IChannelHandlerContext context) => context.Flush(); // 清空缓冲区数据

        /// <summary>
        /// 有异常下线登录的客户端,下线之后通知其他已经在线的客户端
        /// </summary>
        /// <param name="context"></param>
        /// <param name="exception"></param>
        public override void ExceptionCaught(IChannelHandlerContext context, Exception exception)
        {
            // 关闭发生异常的客户端通道
            // context.DisconnectAsync();
            context.CloseAsync();
            // 客户端下线
            BanabaService.logout(sessionid);
            // 通知其他在线客户端
            BanabaService.notifyOutline(sessionid);
        }

        /// <summary>
        /// 对方客户端主动关闭连接触发
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override Task CloseAsync(IChannelHandlerContext context)
        {
            // 客户端下线
            BanabaService.logout(sessionid);
            // 通知其他在线客户端
            BanabaService.notifyOutline(sessionid);
            // 关闭客户端通道
            return base.CloseAsync(context);
        }
    }
}